package com.lazi.june.gateway.filter;

import com.lazi.june.gateway.config.GatewayProperties;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpRequestDecorator;
import org.springframework.lang.NonNull;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.nio.charset.StandardCharsets;
import java.util.Optional;

/**
 * 请求体缓存过滤器
 * <p>
 * 在 Spring WebFlux 响应式编程中，请求体（Request Body）的流只能被读取一次。
 * 如果多个过滤器或业务逻辑需要读取请求体内容，会导致第二次读取时数据已经消费完毕。
 * <p>
 * 该过滤器的作用：
 * 1. 在请求处理的早期阶段读取并缓存请求体
 * 2. 将请求体以字符串形式存储到 ServerWebExchange 的 attributes 中
 * 3. 使用装饰者模式创建新的请求对象，使得下游可以重复读取请求体
 * <p>
 * 使用场景：
 * - 日志过滤器需要记录完整的请求体内容
 * - 签名验证需要读取原始请求体进行校验
 * - 多个过滤器都需要访问请求体数据
 * <p>
 * 缓存策略：
 * - 只缓存 POST、PUT、PATCH 方法的请求（这些方法通常包含请求体）
 * - GET、DELETE 等方法不缓存（通常没有请求体）
 * - 必须有 Content-Type 请求头才缓存
 * <p>
 * 性能考虑：
 * - 请求体会被完整读入内存，大请求体会占用较多内存
 * - 建议配合请求大小限制使用（如 spring.codec.max-in-memory-size）
 * - 可通过配置 june.gateway.cache-request-body=false 禁用此功能
 * <p>
 * 获取缓存的请求体：
 * <pre>
 * String cachedBody = exchange.getAttribute(RequestBodyCacheFilter.CACHED_REQUEST_BODY_ATTR);
 * </pre>
 *
 * @author huliqian
 * @since 2025/8/21
 */
@Slf4j
@Component
@RequiredArgsConstructor
public class RequestBodyCacheFilter implements GlobalFilter, Ordered {

    /**
     * 缓存的请求体在 ServerWebExchange 中的属性名
     * <p>
     * 下游过滤器可以通过此常量获取缓存的请求体字符串：
     * <pre>
     * String body = exchange.getAttribute(CACHED_REQUEST_BODY_ATTR);
     * </pre>
     */
    public static final String CACHED_REQUEST_BODY_ATTR = "CACHED_REQUEST_BODY";

    /**
     * 网关配置属性
     */
    private final GatewayProperties props;

    /**
     * 过滤器核心逻辑：缓存请求体
     * <p>
     * 执行流程：
     * 1. 检查是否启用请求体缓存功能
     * 2. 检查 HTTP 方法是否为 POST/PUT/PATCH
     * 3. 检查是否有 Content-Type 请求头
     * 4. 读取并缓存请求体数据
     * 5. 将请求体保存到 exchange 的 attributes 中
     * 6. 创建装饰请求对象，使下游可以重复读取
     *
     * @param exchange 当前请求的 ServerWebExchange
     * @param chain    过滤器链
     * @return Mono<Void> 异步响应
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        // 1. 检查是否启用请求体缓存
        if (!props.getRequestBodyCache().isEnabled()) {
            return chain.filter(exchange);
        }
        ServerHttpRequest request = exchange.getRequest();
        // 2. 只缓存包含请求体的 HTTP 方法（POST、PUT、PATCH）
        HttpMethod method = request.getMethod();
        if (!(HttpMethod.POST.equals(method) || HttpMethod.PUT.equals(method) || HttpMethod.PATCH.equals(method))) {
            return chain.filter(exchange);
        }
        // 3. 检查是否有 Content-Type 请求头
        MediaType ct = request.getHeaders().getContentType();
        if (ct == null) {
            return chain.filter(exchange);
        }

        // 4. 读取请求体并缓存
        return DataBufferUtils.join(request.getBody())
                .flatMap(dataBuffer -> {
                    // 4.1 将 DataBuffer 读取为字节数组
                    byte[] bytes = new byte[dataBuffer.readableByteCount()];
                    dataBuffer.read(bytes);
                    // 释放原始缓冲区，防止内存泄漏
                    DataBufferUtils.release(dataBuffer);

                    // 4.2 将请求体以字符串形式存储到 exchange 的 attributes 中
                    // 下游过滤器可以通过 CACHED_REQUEST_BODY_ATTR 获取
                    exchange.getAttributes().put(CACHED_REQUEST_BODY_ATTR, new String(bytes, StandardCharsets.UTF_8));

                    // 4.3 创建可重复读取的请求体流
                    // Flux.defer 确保每次调用 getBody() 时都返回新的数据流
                    Flux<DataBuffer> cachedFlux = Flux.defer(() -> Flux.just(exchange.getResponse().bufferFactory().wrap(bytes)));

                    // 4.4 使用装饰者模式创建新的请求对象
                    // 覆盖 getBody() 方法，返回缓存的请求体
                    ServerHttpRequest decorated = new ServerHttpRequestDecorator(request) {
                        @NonNull
                        @Override
                        public Flux<DataBuffer> getBody() {
                            return cachedFlux;
                        }
                    };
                    // 4.5 用装饰后的请求对象替换原请求，继续过滤器链
                    return chain.filter(exchange.mutate().request(decorated).build());
                })
                .switchIfEmpty(chain.filter(exchange)); // 如果没有请求体，继续执行过滤器链
    }

    /**
     * 过滤器执行顺序
     * <p>
     * 返回 -90，表示在大多数过滤器之前执行，但在认证过滤器之后。
     * <p>
     * 执行顺序参考：
     * - CorsFilter: -200 (CORS 跨域)
     * - BlacklistFilter: -110 (黑名单检查)
     * - JwtAuthFilter: -100 (认证)
     * - RequestBodyCacheFilter: -90 (请求体缓存)
     * - RateLimitFilter: -88 (限流)
     * - RequestLoggingFilter: -80 (日志)
     * <p>
     * 这样可以确保：
     * - 在记录日志之前缓存请求体
     * - 在认证之后缓存，避免缓存未授权的请求
     *
     * @return 顺序值，越小越早执行
     */
    @Override
    public int getOrder() {
        // 在日志记录之前，在认证之后执行
        return -90;
    }
}